/**
 * 雪花算法ID生成器
 * 使用BigInt实现，生成64位ID (作为字符串返回)
 */
export class SnowflakeGenerator {
  private static instance: SnowflakeGenerator;
  private nodeId: bigint;
  private sequence: bigint = BigInt(0);
  private lastTimestamp: bigint = BigInt(-1);

  // 时间起点 (2023-01-01)
  private readonly twepoch: bigint = BigInt(1672531200000);

  // 各部分占用的位数
  private readonly workerIdBits: bigint = BigInt(10);
  private readonly sequenceBits: bigint = BigInt(12);

  // 各部分最大值
  private readonly maxWorkerId: bigint = BigInt(-1) ^ (BigInt(-1) << this.workerIdBits);
  private readonly sequenceMask: bigint = BigInt(-1) ^ (BigInt(-1) << this.sequenceBits);

  // 位移
  private readonly workerIdShift: bigint = this.sequenceBits;
  private readonly timestampLeftShift: bigint = this.sequenceBits + this.workerIdBits;

  constructor(nodeId: number = 1) {
    const nodeIdBigInt = BigInt(nodeId);
    if (nodeIdBigInt > this.maxWorkerId || nodeIdBigInt < BigInt(0)) {
      throw new Error(`节点ID不能大于${this.maxWorkerId}或小于0`);
    }
    this.nodeId = nodeIdBigInt;
  }

  /**
   * 获取全局实例
   * @param nodeId 节点ID
   */
  public static getInstance(nodeId: number = 1): SnowflakeGenerator {
    if (!SnowflakeGenerator.instance) {
      SnowflakeGenerator.instance = new SnowflakeGenerator(nodeId);
    }
    return SnowflakeGenerator.instance;
  }

  public nextId(): string {
    let timestamp = this.timeGen();

    if (timestamp < this.lastTimestamp) {
      throw new Error(`Clock moved backwards. Refusing to generate id for ${this.lastTimestamp - timestamp}`);
    }

    if (this.lastTimestamp === timestamp) {
      this.sequence = (this.sequence + BigInt(1)) & this.sequenceMask;
      if (this.sequence === BigInt(0)) {
        timestamp = this.tilNextMillis(this.lastTimestamp);
      }
    } else {
      this.sequence = BigInt(0);
    }

    this.lastTimestamp = timestamp;

    // 使用BigInt组合成64位ID
    const id = ((timestamp - this.twepoch) << this.timestampLeftShift) |
      (this.nodeId << this.workerIdShift) |
      this.sequence;

    return id.toString();
  }

  private tilNextMillis(lastTimestamp: bigint): bigint {
    let timestamp = this.timeGen();
    while (timestamp <= lastTimestamp) {
      timestamp = this.timeGen();
    }
    return timestamp;
  }

  private timeGen(): bigint {
    return BigInt(Date.now());
  }
}
